#include "lab.h"

#include <cassert>
#include <vector>

/***********
 * Upgrade *
 ***********/

Upgrade::Upgrade( UpgradeType aType, const std::string & aName, int64_t aPrice, int64_t aLevel )
    : type( aType )
    , name( aName )
    , price( aPrice )
    , level( aLevel ) {}

Upgrade::Upgrade( const Upgrade & other )
    : type( other.getType() )
    , name( other.getName() )
    , price( other.getPrice() )
    , level( other.getLevel() )
    , priority( other.getPriority() ) {}

Upgrade & Upgrade::operator=( const Upgrade & other ) noexcept {
    assert( other.getType() == type );
    level = other.getLevel();
    priority = other.getPriority();
    return *this;
}

double Upgrade::diffPrice( int64_t price, int64_t first, int64_t last ) {
    assert( first <= last );
    return static_cast<double>( price ) * static_cast<double>( ( last * ( last + 1 ) * ( 2 * last + 1 ) - first * ( first + 1 ) * ( 2 * first + 1 ) ) ) / 6;
}

double Upgrade::upgradePrice( int64_t n ) const {
    return diffPrice( price, level, level + n );
}

double Upgrade::upgradePrice( int64_t begin, int64_t end ) const {
    return diffPrice( price, begin, end );
}

int64_t Upgrade::upgrade( int64_t aLevel, bool overwrite ) {
    level = aLevel + !overwrite * level;
    return level;
}

int64_t Upgrade::getLevel() const {
    return level;
}

const std::string & Upgrade::getName() const {
    return name;
}

const std::string & Upgrade::getNameByType( UpgradeType type ) {
    return upgradeNames[static_cast<size_t>(type)];
}

UpgradeType Upgrade::getType() const {
    return type;
}

double Upgrade::getPriority() const {
    return priority;
}

int64_t Upgrade::getPrice() const {
    return price;
}

void Upgrade::setPriority( double aPriority ) {
    priority = aPriority;
}

/***********
 * Product *
 ***********/

Product::Product( ProductType aType, const std::string & aName )
    : type( aType )
    , name( aName ) {}

Product::Product( const Product & other )
    : type( other.type )
    , name( other.name ) {
    operator=( other );
}

Product & Product::operator=( const Product & other ) noexcept {
    assert( other.type == type );
    factor = other.factor;
    weight = other.weight;
    value = other.value;
    return *this;
}

/************
 * PowerOrb *
 ************/
int64_t PowerOrb::claim( int aCount ) {
    assert( aCount >= 0 && aCount < maxLevel );

    static const auto list = priceList();
    int64_t price = 0;
    if ( aCount > count )
    {
        for ( int level = count + 1; level <= aCount; level++ )
        {
            price += list[static_cast<size_t>( level )];
        }
    } else
    {
        for ( int level = aCount + 1; level <= count; level++ )
        {
            price -= list[static_cast<size_t>( level )];
        }
    }

    count = aCount;
    return price;
}

int64_t PowerOrb::value() const {
    static const auto list = priceList();
    int64_t sum = 0;
    for ( int level = 1; level <= count; ++level )
    {
        sum += list[static_cast<size_t>( level )];
    }
    return sum;
}

std::string doubleToUnits( double num ) {
    // TODO in gcc > 11 move to std::to_chars
    auto toS = []( double v, const char * fmt ) {
        std::vector<char> s( static_cast<size_t>( snprintf( nullptr, 0, fmt, v ) + 1 ) );
        snprintf( s.data(), s.size(), fmt, v );
        return std::string( s.data() );
    };
    constexpr double step = 1e3;
    constexpr std::array<char, 5> suffix{ ' ', 'K', 'M', 'B', 'T' };
    if ( num >= step )
    {
        for ( char c : suffix )
        {
            if ( num / step < 0.1 )
            {
                return toS( num, "%0.2g" ) + ' ' + c;
            } else
            { num /= step; }
        }
    }
    return toS( num, "%0.0f" );
}
